package pacman.actors;

import org.newdawn.slick.Animation;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.pathfinding.Path.Step;

import pacman.base.Actor;
import pacman.base.Body;
import pacman.base.Entity;
import pacman.base.Level;
import pacman.brains.GoToBaseGhostBrain;
import pacman.brains.RunningGhostBrain;
import pacman.map.Map;
import pacman.util.Collider;
import pacman.util.SheetUtil;
import pacman.util.StateManager;

public class Ghost extends Body implements Actor {
    public Animation sprite;								//Sprite của Ghost - Là một chuỗi các Image
    private static final float SPEED = 0.04f;				//Tốc độ di chuyển
    private Brain brain;									//Não của Ghost - Chức năng tìm đường
    private StateManager manager;							//Quản lý - truy cậptrạng thái
    private SpriteSheet sheet;								//Bảng các image của Ghost
    private String lastDir;
    private Map parent;										//Map chưa vật thể này
    private Brain normalBrain;								
    private boolean toRemove = false;
    private int rowAnim;
    public enum State {										//Các Trạng thái của một Ghost
    	NORMAL, WAIT, EATABLE, DEATH;
    }

    public Ghost(Map parent, Role role, Shape shape, Brain normalBrain, int rowAnim) throws SlickException {
		super(role, shape);									//Role + Shape
		this.parent = parent;								//Map chứa
		this.normalBrain = normalBrain;						//Bộ não
		this.rowAnim = rowAnim;								//Thứ tự sprite ở trong cái ảnh
		init();												//Khởi tạo
    }
    //------------------------------------------------//
    private void init() {
		try {
		    sheet = new SpriteSheet("data/ghosts.png", 32, 32);	//Load spritesheet ghost.png
		    lastDir = "right";									//mặc định là right
		    manager = new StateManager();						//Manager là một linked List chứa các trạng thái
		    manager.add(new NormalState());						//Thêm Normal State
		    manager.add(new WaitState());						//Thêm Wait State
		    manager.add(new EatableState());					//Thêm Eatable State
		    manager.add(new DeathState());						//Thêm Death State
		} catch (Exception e) {									//Các method trong State đã được Overide
		    e.printStackTrace();								//tức là các state sẽ có hàm render và update tương ứng
		}
    }
    
    public void render(BasicGameState bsg, Graphics g) {
    	manager.render(g);										//Render tùy theo trạng thái >> State.Render
    }

    public void update(GameContainer game, int delta) {
    	manager.update(game, delta);							//Update tùy theo trạng thái State.Update
    }
    //------------------------------------------------//

    public void addToLevel(Level l) {
    	l.add(this);											//Xóa khỏi level
    }	

    public Role getRole() {
    	return Entity.Role.GHOST;
    }

    public void onCollision(Entity obstacle) {
    	manager.onCollision(obstacle);							//Kiểm tra va chạm và xử lí tùy theo State của Ghost
    }

    public void removeFromLevel(Level l) {
    	l.remove(this);
    }

    public boolean isToRemove() {
    	return toRemove;
    }
    //---------------- NORMAL STATE ----------------//
    private class NormalState implements pacman.util.State {					//Trạng Thái Normal
    	private Animation up, down, left, right;								//Animation lên, xuống, trái phải

		public boolean equals(Object state) {
		    return state == State.NORMAL;
		}
		public void enter() {
		    right = SheetUtil.getAnimationFromSheet(sheet, rowAnim, 0, 7);		//Vì mỗi Row là một màu của Ghost
		    left = SheetUtil.getAnimationFromSheet(sheet, rowAnim, 0, 7);		//rowAnim để chỉ column chứa màu tương ứng của Ghost
		    up = SheetUtil.getAnimationFromSheet(sheet, rowAnim, 0, 7);
		    down = SheetUtil.getAnimationFromSheet(sheet, rowAnim, 0, 7);
	
		    doNormalAnim();
	
		    brain = normalBrain;
		    brain.setCurrent(getPosition());
		    brain.init();
	
		}
		
		public void render(Graphics g) {
		    sprite.draw(getX(), getY());
		}

		public void update(GameContainer game, int delta) {
		    Collider.testAndAlert(getBody(), Map.getPlayer());
		    try {
				brain.update(delta);
				doMovement(brain.getCurrentStep(), delta);
		    } catch (Exception e) {
		    	manager.enter(State.WAIT);
		    }
		}

		private void doNormalAnim() {
		    if (lastDir.equalsIgnoreCase("up")) sprite = up;
		    if (lastDir.equalsIgnoreCase("right")) sprite = right;
		    if (lastDir.equalsIgnoreCase("left")) sprite = left;
		    if (lastDir.equalsIgnoreCase("down")) sprite = down;
		}

		private void doMovement(Step step, int delta) {
		    float cx = shape.getX();										//Tọa Độ X hiện tại của Ghost
		    float cy = shape.getY();										//Tọa Độ Y hiện tại của Ghost
		    float diffX = cx - step.getX() * 32;							//Tìm độ biến thiên X với Step tiếp theo
		    float diffY = cy - step.getY() * 32;							//Tìm độ biến thien Y với Step tiếp theo
	
		    if (Math.abs(diffX) < 1 && Math.abs(diffY) < 1) {				//Nếu |x| và |y| < 1, thì ok 
		    	shape.setX(step.getX() * Map.SIZE);							//Chỉ cần thay đổi vị trí X và Y
		    	shape.setY(step.getY() * Map.SIZE);	
		    	brain.goToNextStep(getPosition());							//Chuyển Sang Bước tiếp theo
		    } else {
				boolean keyRight = false;									//Biểu thị 4 hướng di chuyển của Ghost
				boolean keyLeft = false;									//Left - Right
				boolean keyUp = false;										//Up - Down
				boolean keyDown = false;
			
				float dx = cx - step.getX() * 32;							//Tìm lại độ biến thiên dX, dY giữa Ghost và Step
				float dy = cy - step.getY() * 32;							//Lúc này thì |x| và |y| chắc chắn sẽ > 1 
				if (dx < 0) keyRight = true;								
				if (dx > 0) keyLeft = true;									//Nếu dx < 0 tức là Ghost di chuyển sang phải và ngược lại		
				if (dy < 0) keyDown = true;									//Nếu dy < 0 tức là Ghost di chuyển xuống dưới và ngược lại
				if (dy > 0) keyUp = true;
				
				if (keyUp) {												//Nếu Ghost là đi lên
				    sprite = up;											//Đổi Sprite thành Up
				    sprite.update(delta);									//update sprite theo delta
				    float y = getY() - delta * SPEED;						//Đi lên thì chỉ có tọa độ Y giảm, Y = Y(hiện tại) - delta*speed
				    shape.setY(y);											//Set Y mới cho Ghost
				    lastDir = "up";											//Lưu lại vị trí trước của Ghost
				} 
			    if (keyDown) {												//Nếu Ghost là đi xuống
					sprite = down;											//Đổi Sprite thành down
					sprite.update(delta);									//
					float y = getY() + delta * SPEED;
					shape.setY(y);											//Set Lại Y
					lastDir = "down";										//Lưu lại vị trí trước của Ghost là " donw"
			    }
				if (keyLeft) {												//Nếu Ghost là Sang trái
				    sprite = left;											//Đổi Sprite thành Left
				    sprite.update(delta);
				    float x = getX() - delta * SPEED;						//Sang trái thì giảm X
				    shape.setX(x);
				    lastDir = "left";
					}
			    if (keyRight) {
					sprite = right;
					sprite.update(delta);
					float x = getX() + delta * SPEED;
					shape.setX(x);
					lastDir = "right";
			    }
			}
	    }
		public void onCollision(Entity obstacle) {
			//Ghost nếu va chạm với Entity khác thì ko có gì xảy ra hết :D
		}

    }
    //---------------- NORMAL STATE ----------------//
    
    //---------------- WAIT STATE ----------------//
    private class WaitState implements pacman.util.State {
    	private int timer;								//Timer là 1 kiểu xử lí Thread Đơn giản nhất
    	
		public boolean equals(Object state) {	
		    return state == State.WAIT;
		}
		
		public void enter() {
		    timer = 0;									//Lúc vào Trạng thái chờ thì set Timer = 0ms
		}

		public void onCollision(Entity obstacle) {
			//Ghost va chạm với Entity khác thì ko có gì xảy ra
		}

		public void render(Graphics g) {
		    sprite.draw(getX(), getY());				//Hàm Render sẽ được render trong Level
		}

		public void update(GameContainer gc, int delta) {
		    timer = timer + delta;
		    if (timer > 1000) {
				if (manager.getPreviousState().equals(State.NORMAL)) manager.enter(State.NORMAL);
				if (manager.getPreviousState().equals(State.EATABLE)) manager.enter(State.EATABLE);
				manager.enter(State.NORMAL);
		    }
		}

    }
    
    private class EatableState implements pacman.util.State {

	private int timer;

	private Animation eatedUp, eatedDown, eatedLeft, eatedRight;

	public boolean equals(Object state) {
	    return state == State.EATABLE;
	}

	public void enter() {
	    // load eated animations
	    eatedRight = SheetUtil.getAnimationFromSheet(sheet, 4, 0, 7);
	    eatedLeft = SheetUtil.getAnimationFromSheet(sheet, 4, 0, 7);
	    eatedUp = SheetUtil.getAnimationFromSheet(sheet, 4, 0, 7);
	    eatedDown = SheetUtil.getAnimationFromSheet(sheet, 4, 0, 7);

	    doEatAnim();

	    try {
		brain = new RunningGhostBrain(parent, getPosition());
	    } catch (SlickException e) {
		Log.error(e);
	    }

	    timer = 0;
	}

	public void onCollision(Entity obstacle) {
	    if (obstacle.getRole().equals(Role.PLAYER)) {
		manager.enter(State.DEATH);
	    }
	}

	public void render(Graphics g) {
	    sprite.draw(getX(), getY());
	}

	public void update(GameContainer game, int delta) {
	    timer = timer + delta;
	    if (timer > 10000) {
		manager.enter(State.NORMAL);
	    }

	    // Check Collision with player
	    Collider.testAndAlert(getBody(), Map.getPlayer());
	    // thinking about next move
	    try {
		brain.update(delta);
		doMovement(brain.getCurrentStep(), delta);
	    } catch (Exception e) {
		manager.enter(State.WAIT);
	    }
	}

	private void doEatAnim() {
	    if (lastDir.equalsIgnoreCase("up")) {
		sprite = eatedUp;
	    } else {
		if (lastDir.equalsIgnoreCase("right")) {
		    sprite = eatedRight;
		} else {
		    if (lastDir.equalsIgnoreCase("left")) {
			sprite = eatedLeft;
		    } else {
			if (lastDir.equalsIgnoreCase("down")) {
			    sprite = eatedDown;
			} else {
			    Log
				    .error("Player.doEatAnim - impossibile to determine current anim");
			    return;
			}
		    }
		}

	    }
	}

	/**
	 * Do movement based on delta and current step
	 * 
	 * @param step
	 * @param delta
	 */
	private void doMovement(Step step, int delta) {
	    // check if reached position of step
	    float cx = shape.getX();
	    float cy = shape.getY();

	    float diffX = cx - step.getX() * 32;
	    float diffY = cy - step.getY() * 32;

	    if (Math.abs(diffX) < 1 && Math.abs(diffY) < 1) {
		// goto next step
		shape.setX(step.getX() * Map.SIZE);
		shape.setY(step.getY() * Map.SIZE);
		// currentStep++;
		brain.goToNextStep(getPosition());
	    } else {
		boolean keyRight = false;
		boolean keyLeft = false;
		boolean keyUp = false;
		boolean keyDown = false;
		// try to find where i must move
		float dx = cx - step.getX() * 32;
		float dy = cy - step.getY() * 32;
		if (dx < 0) {
		    keyRight = true;
		} else {
		    if (dx > 0) {
			keyLeft = true;
		    }
		}
		if (dy < 0) {
		    keyDown = true;
		} else {
		    if (dy > 0) {
			keyUp = true;
		    }
		}
		// do movement
		if (keyUp) {
		    sprite = eatedUp;
		    sprite.update(delta);
		    float y = getY() - delta * SPEED;
		    shape.setY(y);
		    lastDir = "up";
		} else {
		    if (keyDown) {
			sprite = eatedDown;
			sprite.update(delta);
			float y = getY() + delta * SPEED;
			shape.setY(y);
			lastDir = "down";
		    } else {
			if (keyLeft) {
			    sprite = eatedLeft;
			    sprite.update(delta);
			    float x = getX() - delta * SPEED;
			    shape.setX(x);
			    lastDir = "left";
			} else {
			    if (keyRight) {
				sprite = eatedRight;
				sprite.update(delta);
				float x = getX() + delta * SPEED;
				shape.setX(x);
				lastDir = "right";
			    }
			}
		    }
		}
	    }
	}

    }

    private class DeathState implements pacman.util.State {

	private Animation deathUp, deathDown, deathLeft, deathRight;

	public boolean equals(Object state) {
	    return state == State.DEATH;
	}

	public void enter() {
	    // load eated animations
	    deathRight = SheetUtil.getAnimationFromSheet(sheet, 5, 0, 7);
	    deathLeft = SheetUtil.getAnimationFromSheet(sheet, 5, 0, 7);
	    deathUp = SheetUtil.getAnimationFromSheet(sheet, 5, 0, 7);
	    deathDown = SheetUtil.getAnimationFromSheet(sheet, 5, 0, 7);

	    doDeathAnim();

	    try {
		brain = new GoToBaseGhostBrain(parent, getPosition());
	    } catch (SlickException e) {
		Log.error(e);
	    }
	}

	public void onCollision(Entity obstacle) {
	}

	public void render(Graphics g) {
	    sprite.draw(getX(), getY());
	}

	public void update(GameContainer game, int delta) {
	    if (brain.isCannotFindPath()) {
		manager.enter(State.NORMAL);
	    }

	    // thinking about next move
	    try {
		brain.update(delta);
		doMovement(brain.getCurrentStep(), delta);
	    } catch (Exception e) {
		manager.enter(State.WAIT);
	    }
	}

	private void doDeathAnim() {
	    if (lastDir.equalsIgnoreCase("up")) {
		sprite = deathUp;
	    } else {
		if (lastDir.equalsIgnoreCase("right")) {
		    sprite = deathRight;
		} else {
		    if (lastDir.equalsIgnoreCase("left")) {
			sprite = deathLeft;
		    } else {
			if (lastDir.equalsIgnoreCase("down")) {
			    sprite = deathDown;
			} else {
			    Log
				    .error("Ghost.doDeathAnim - impossibile to determine current anim");
			    return;
			}
		    }
		}

	    }
	}

	private void doMovement(Step step, int delta) {
	    // check if reached position of step
	    float cx = shape.getX();
	    float cy = shape.getY();

	    float diffX = cx - step.getX() * 32;
	    float diffY = cy - step.getY() * 32;

	    if (Math.abs(diffX) < 1 && Math.abs(diffY) < 1) {
		// goto next step
		shape.setX(step.getX() * Map.SIZE);
		shape.setY(step.getY() * Map.SIZE);
		// currentStep++;
		brain.goToNextStep(getPosition());
	    } else {
		boolean keyRight = false;
		boolean keyLeft = false;
		boolean keyUp = false;
		boolean keyDown = false;
		// try to find where i must move
		float dx = cx - step.getX() * 32;
		float dy = cy - step.getY() * 32;
		if (dx < 0) {
		    keyRight = true;
		} else {
		    if (dx > 0) {
			keyLeft = true;
		    }
		}
		if (dy < 0) {
		    keyDown = true;
		} else {
		    if (dy > 0) {
			keyUp = true;
		    }
		}
		// do movement
		if (keyUp) {
		    sprite = deathUp;
		    sprite.update(delta);
		    float y = getY() - delta * SPEED;
		    shape.setY(y);
		    lastDir = "up";
		} else {
		    if (keyDown) {
			sprite = deathDown;
			sprite.update(delta);
			float y = getY() + delta * SPEED;
			shape.setY(y);
			lastDir = "down";
		    } else {
			if (keyLeft) {
			    sprite = deathLeft;
			    sprite.update(delta);
			    float x = getX() - delta * SPEED;
			    shape.setX(x);
			    lastDir = "left";
			} else {
			    if (keyRight) {
				sprite = deathRight;
				sprite.update(delta);
				float x = getX() + delta * SPEED;
				shape.setX(x);
				lastDir = "right";
			    }
			}
		    }
		}
	    }
	}

    }

    public void setEatable() {
    	manager.enter(State.EATABLE);
    }

    private Body getBody() {
    	return this;
    }

    public pacman.util.State getState() {
    	return (pacman.util.State) manager.currentState();
    }

    public void setToRemove() {
    	toRemove = true;
    }

    public Brain getBrain() {
    	return brain;
    }
}
